/*******************************************************************************
 * Copyright 2011 Mauro Luigi Drago (drago.luigi@gmail.com)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *******************************************************************************/
package com.bytenose.extrautils.collect;

import java.util.Iterator;
import java.util.NoSuchElementException;

import com.bytenose.extrautils.annotations.NonNullable;
import com.bytenose.extrautils.annotations.UtilityClass;
import com.bytenose.extrautils.functional.Method2;
import com.bytenose.extrautils.functional.Method3;
import com.bytenose.extrautils.functional.Method4;

/**
 * Defines wise iteration methods. 
 * @author Mauro Luigi Drago
 *
 */
@UtilityClass
final class WiseIterators {
	
	/**
	 * Private constructor to avoid instantiation.
	 */
	private WiseIterators() {
	}
	
	/**
	 * Iterates stepwise over a set of iterators and applies a specified method 
	 * to each element tuple.
	 * The iterators must yield the same number of elements, 
	 * otherwise an exception is raised.
	 * @param <T1> the type of the first argument.
	 * @param <T2> the type of the second argument.
	 * @param it1 the first iterator, cannot be null.
	 * @param it2 the second iterator, cannot be null.
	 * @param method the method to apply, cannot be null.
	 * 
	 * @throws NoSuchElementException 
	 * 		if iterators do not yield the same number of elements.
	 * @throws IllegalArgumentException 
	 * 		if some of the {@link NonNullable} arguments are null.
	 */
	static <T1, T2> void applyWise(
			@NonNullable Iterator<T1> it1, @NonNullable Iterator<T2> it2, 
			@NonNullable Method2<T1,T2> method)
			throws IllegalArgumentException, NoSuchElementException {
		if (it1 == null) throw new IllegalArgumentException("it1");
		if (it2 == null) throw new IllegalArgumentException("it2");
		if (method == null) throw new IllegalArgumentException("method");
		
		while (it1.hasNext()) {
			T1 arg1 = it1.next();
			
			if (!it2.hasNext()) 
				throw new NoSuchElementException("it1");
			T2 arg2 = it2.next();
			
			method.apply(arg1, arg2);
		}
		if (it2.hasNext()) throw new NoSuchElementException("it2");
	}
	
	/**
	 * Iterates stepwise over a set of iterators and applies a specified method 
	 * to each element tuple.
	 * The iterators must yield the same number of elements, 
	 * otherwise an exception is raised.
	 * @param <T1> the type of the first argument
	 * @param <T2> the type of the second argument.
	 * @param <T3> the type of the third argument.
	 * @param it1 the first iterator, cannot be null.
	 * @param it2 the second iterator, cannot be null.
	 * @param it3 the third iterator, cannot be null.
	 * @param method the method to apply, cannot be null.
	 * 
	 * @throws NoSuchElementException 
	 * 		if iterators do not yield the same number of elements.
	 * @throws IllegalArgumentException 
	 * 		if some of the {@link NonNullable} arguments are null.
	 */
	static <T1, T2, T3> void applyWise(
			@NonNullable Iterator<T1> it1, @NonNullable Iterator<T2> it2, 
			@NonNullable Iterator<T3> it3,
			@NonNullable Method3<T1,T2,T3> method)
			throws IllegalArgumentException, NoSuchElementException {
		if (it1 == null) throw new IllegalArgumentException("it1");
		if (it2 == null) throw new IllegalArgumentException("it2");
		if (it3 == null) throw new IllegalArgumentException("it3");
		if (method == null) throw new IllegalArgumentException("method");
		
		while (it1.hasNext()) {
			T1 arg1 = it1.next();
			
			if (!it2.hasNext()) 
				throw new NoSuchElementException("it2");
			T2 arg2 = it2.next();
			
			if (!it3.hasNext()) 
				throw new NoSuchElementException("it3");
			T3 arg3 = it3.next();
			
			method.apply(arg1, arg2, arg3);
		}
		if (it2.hasNext()) throw new NoSuchElementException("it2");
		if (it3.hasNext()) throw new NoSuchElementException("it3");
	}
	
	/**
	 * Iterates stepwise over a set of iterators and applies a specified method 
	 * to each element tuple.
	 * The iterators must yield the same number of elements, 
	 * otherwise an exception is raised.
	 * @param <T1> the type of the first argument.
	 * @param <T2> the type of the second argument.
	 * @param <T3> the type of the third argument.
	 * @param <T4> the type of the fourth argument.
	 * @param it1 the first iterator, cannot be null.
	 * @param it2 the second iterator, cannot be null.
	 * @param it3 the third iterator, cannot be null.
	 * @param it4 the fourth iterator, cannot be null.
	 * @param method the method to apply, cannot be null.
	 * 
	 * @throws NoSuchElementException 
	 * 		if iterators do not yield the same number of elements.
	 * @throws IllegalArgumentException 
	 * 		if some of the {@link NonNullable} arguments are null.
	 */
	static <T1, T2, T3, T4> void applyWise(
			@NonNullable Iterator<T1> it1, @NonNullable Iterator<T2> it2, 
			@NonNullable Iterator<T3> it3, @NonNullable Iterator<T4> it4, 
			@NonNullable Method4<T1,T2,T3, T4> method)
			throws IllegalArgumentException, NoSuchElementException {
		if (it1 == null) throw new IllegalArgumentException("it1");
		if (it2 == null) throw new IllegalArgumentException("it2");
		if (it3 == null) throw new IllegalArgumentException("it3");
		if (it4 == null) throw new IllegalArgumentException("it4");
		if (method == null) throw new IllegalArgumentException("method");
		
		while (it1.hasNext()) {
			T1 arg1 = it1.next();
			
			if (!it2.hasNext()) 
				throw new NoSuchElementException("it2");
			T2 arg2 = it2.next();
			
			if (!it3.hasNext()) 
				throw new NoSuchElementException("it3");
			T3 arg3 = it3.next();
			
			if (!it4.hasNext()) 
				throw new NoSuchElementException("it4");
			T4 arg4 = it4.next();
			
			method.apply(arg1, arg2, arg3, arg4);
		}
		if (it2.hasNext()) throw new NoSuchElementException("it2");
		if (it3.hasNext()) throw new NoSuchElementException("it3");
		if (it4.hasNext()) throw new NoSuchElementException("it4");
	}
}
